/*******************************************************************************
 * Copyright (c) 2016 Bosch Software Innovations GmbH and others.
 * 
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * and Eclipse Distribution License v1.0 which accompany this distribution.
 * 
 * The Eclipse Public License is available at
 *    http://www.eclipse.org/legal/epl-v20.html
 * and the Eclipse Distribution License is available at
 *    http://www.eclipse.org/org/documents/edl-v10.html.
 * 
 * Contributors:
 *    Achim Kraus (Bosch Software Innovations GmbH) - initial implementation
 *                                                    stuff copied from TcpConnectorTest
 *    Achim Kraus (Bosch Software Innovations GmbH) - adjust creation of oubound message
 *                                                    with null correlation context.
 *    Achim Kraus (Bosch Software Innovations GmbH) - add sending correlation context.
 *    Achim Kraus (Bosch Software Innovations GmbH) - replace "any/0.0.0.0" with 
 *                                                    "localhost/127.0.0.1" in destination.
 *    Achim Kraus (Bosch Software Innovations GmbH) - add create server address
 *                                                    using LoopbackAddress.
 ******************************************************************************/
package org.eclipse.californium.elements.tcp.netty;

import java.io.ByteArrayOutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;

import org.eclipse.californium.elements.AddressEndpointContext;
import org.eclipse.californium.elements.Connector;
import org.eclipse.californium.elements.EndpointContext;
import org.eclipse.californium.elements.MessageCallback;
import org.eclipse.californium.elements.RawData;
import org.eclipse.californium.elements.config.Configuration;
import org.eclipse.californium.elements.config.TcpConfig;
import org.eclipse.californium.elements.rule.ThreadsRule;

/**
 * Utils for TCP based connector tests.
 */
public class ConnectorTestUtil {

	public static final int NUMBER_OF_THREADS = 1;
	public static final int CONNECTION_TIMEOUT_IN_MS = 100;
	public static final int IDLE_TIMEOUT_IN_S = 100;
	public static final int IDLE_TIMEOUT_RECONNECT_IN_S = 2;
	public static final int CONTEXT_TIMEOUT_IN_MS = 1000;
	public static final int CATCHER_TIMEOUT_IN_MS = 2000;
	public static final int TEST_TIMEOUT_IN_MS = 25000;

	private static final Random random = new Random(0);

	public static final ThreadsRule THREADS_RULE = new ThreadsRule("ObjectCleanerThread", "globalEventExecutor-.*");

	static {
		TcpConfig.register();
		Configuration configuration = new Configuration();
		// Ensure to create the thread groups before the Timeout rule.
		// Otherwise ThreadGroup.destroy() in FailOnTimeout.evaluate()
		// would destroy them and cause failures when reusing the destroyed
		// thread-groups.
		// see https://github.com/junit-team/junit4/pull/1517
		new TcpClientConnector(configuration);
		new TcpServerConnector(null, configuration);
	}

	public static void stop(List<Connector> list) {
		for (Connector connector : list) {
			connector.stop();
		}
		list.clear();
	}
	
	public static Configuration getTestConfiguration() {
		Configuration configuration = new Configuration();
		configuration.set(TcpConfig.TCP_WORKER_THREADS, NUMBER_OF_THREADS);
		configuration.set(TcpConfig.TCP_CONNECT_TIMEOUT, CONNECTION_TIMEOUT_IN_MS, TimeUnit.MILLISECONDS);
		configuration.set(TcpConfig.TCP_CONNECTION_IDLE_TIMEOUT, IDLE_TIMEOUT_IN_S, TimeUnit.SECONDS);
		return configuration;
	}

	public static InetSocketAddress createServerAddress(int port) {
		return new InetSocketAddress(InetAddress.getLoopbackAddress(), port);
	}

	public static InetSocketAddress getDestination(InetSocketAddress server) {
		if (server.getAddress().isAnyLocalAddress()) {
			// for destination replace any by localhost
			server = new InetSocketAddress(InetAddress.getLoopbackAddress(), server.getPort());
		}
		return server;
	}

	public static RawData createMessage(int messageSize, EndpointContext contextToSent, MessageCallback callback)
			throws Exception {
		byte[] data = new byte[messageSize];
		random.nextBytes(data);

		try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
			if (messageSize < 13) {
				stream.write(messageSize << 4);
			} else if (messageSize < (1 << 8) + 13) {
				stream.write(13 << 4);
				stream.write(messageSize - 13);
			} else if (messageSize < (1 << 16) + 269) {
				stream.write(14 << 4);

				ByteBuffer buffer = ByteBuffer.allocate(2);
				buffer.putShort((short) (messageSize - 269));
				stream.write(buffer.array());
			} else {
				stream.write(15 << 4);

				ByteBuffer buffer = ByteBuffer.allocate(4);
				buffer.putInt(messageSize - 65805);
				stream.write(buffer.array());
			}

			stream.write(1); // GET
			stream.write(data);
			stream.flush();

			return RawData.outbound(stream.toByteArray(), contextToSent, callback, false);
		}
	}

	public static RawData createMessage(InetSocketAddress address, int messageSize, MessageCallback callback)
			throws Exception {
		return createMessage(messageSize, new AddressEndpointContext(getDestination(address)), callback);
	}
}
